/**
 * This class contains methods that are necessary when parsing an rss feed.
 * @author Chirag Mistry
 */

package ca.uwo.android.rss;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;

import android.content.Context;
import ca.uwo.android.sslconnection.WesternHttpClient;

public class RssFeedParser {

	// Windows characters 
	// u"\x80": u"\u20AC", # EURO SIGN
	// u"\x82": u"\u201A", # SINGLE LOW-9 QUOTATION MARK
	// u"\x83": u"\u0192", # LATIN SMALL LETTER F WITH HOOK
	// u"\x84": u"\u201E", # DOUBLE LOW-9 QUOTATION MARK
	// u"\x85": u"\u2026", # HORIZONTAL ELLIPSIS
	// u"\x86": u"\u2020", # DAGGER
	// u"\x87": u"\u2021", # DOUBLE DAGGER
	// u"\x88": u"\u02C6", # MODIFIER LETTER CIRCUMFLEX ACCENT
	// u"\x89": u"\u2030", # PER MILLE SIGN
	// u"\x8A": u"\u0160", # LATIN CAPITAL LETTER S WITH CARON
	// u"\x8B": u"\u2039", # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
	// u"\x8C": u"\u0152", # LATIN CAPITAL LIGATURE OE
	// u"\x8E": u"\u017D", # LATIN CAPITAL LETTER Z WITH CARON
	// u"\x91": u"\u2018", # LEFT SINGLE QUOTATION MARK
	// u"\x92": u"\u2019", # RIGHT SINGLE QUOTATION MARK
	// u"\x93": u"\u201C", # LEFT DOUBLE QUOTATION MARK
	// u"\x94": u"\u201D", # RIGHT DOUBLE QUOTATION MARK
	// u"\x95": u"\u2022", # BULLET
	// u"\x96": u"\u2013", # EN DASH
	// u"\x97": u"\u2014", # EM DASH
	// u"\x98": u"\u02DC", # SMALL TILDE
	// u"\x99": u"\u2122", # TRADE MARK SIGN
	// u"\x9A": u"\u0161", # LATIN SMALL LETTER S WITH CARON
	// u"\x9B": u"\u203A", # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
	// u"\x9C": u"\u0153", # LATIN SMALL LIGATURE OE
	// u"\x9E": u"\u017E", # LATIN SMALL LETTER Z WITH CARON
	// u"\x9F": u"\u0178", # LATIN CAPITAL LETTER Y WITH DIAERESIS

	/**
	 * All other characters
	 */

	protected static final String MS_PROBLEMS = "[\u201A\u0192\u201E\u2026\u2020\u2021\u02C6\u2030\u0160\u2039\u0152\u2018\u2019\u201C\u201D\u2022\u2013\u2014\u02DC\u2122\u0161\u203A\u0153\u0178]";

	protected static final String NON_ALPHANUM_AND_SELECTPUNCT_REPLACE_REGEX = "[^\\w\\s\\.\\-,'\"<>?:/!\\[\\]=)(#]";
	// protected static final String NON_ALPHANUM_AND_SELECTPUNCT_REPLACE_REGEX
	// =
	// "[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD\\u10000-\\u10FFFF]";

	protected static final String DOUBLE_SMART_QUOTES_REGEX = "[\u201C\u201D\u0093\u0094]";
	protected static final String SINGLE_SMART_QUOTES_REGEX = "[\u2018\u2019\u008B\u009B\u0082\u0091\u0092]";
	protected static final String DASH_REGEX = "[\u0095-\u0098\u2014\u2013]";
	// protected static final String ALL_OTHER_MS_INVALID_CHARS =
	// "[\\u0080\\u0083\\u0084\\u0085\\u0086\\u0087\\u0088\\u0089\\u008A\\u008C\\u008E\\u0095\\u0096\\u0097\\u0098\\u0099\\u009A\\u009C\\u009E\\u009F]";
	//

	/**
	 * Run through this regex after replacing known problem characters e.g. MS
	 * smart quotes as a final check so the feed does not crash
	 */
	protected static final String INVALID_XML = "[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD\\u10000-\\u10FFFF]";

	// protected static final String INVALID_XML = "[Ox80-Ox9F]";

	// common tags that are encountered when parsing
	protected static final String RDF = "rdf";
	protected static final String RSS = "rss";
	protected static final String CHANNEL = "channel";
	protected static final String ITEM = "item";
	protected static final String TITLE = "title";
	protected static final String LINK = "link";
	protected static final String PUBDATE = "pubDate";
	protected static final String DATE = "date";
	protected static final String DESCRIPTION = "description";
	protected static final String IMAGE = "enclosure";

	protected final URL rssFeedURL; // the url of the feed to parse
	private WesternHttpClient httpClient;

	/**
	 * The constructor of the rss feed parser. Creates a URL object from the
	 * parameter url.
	 * 
	 * @param rssFeedURL
	 *            the URL of the rss feed
	 */
	protected RssFeedParser(String rssFeedURL) throws Exception {
		this(rssFeedURL, null);
	}

	/**
	 * 
	 * @param rssFeedURL
	 *            the URL of the rss feed
	 * @param context
	 *            Context object which allows access to application-specific
	 *            resources and classes.
	 * @throws Exception
	 *             Exception to be thrown
	 */
	protected RssFeedParser(String rssFeedURL, Context context)
			throws Exception {
		this.rssFeedURL = new URL(rssFeedURL);
		this.httpClient = new WesternHttpClient(context);
	}

	/**
	 * This method is used to open a connection to the rss feed and return an
	 * input stream to it for reading / parsing
	 * 
	 * @return InputStream input stream from rss feed
	 */
	protected InputStream getInputStream() throws Exception {
		if (!rssFeedURL.getProtocol().equals("https")) {
			return rssFeedURL.openConnection().getInputStream();
		} else {
			return getInputStreamFromHTTPS();
		}
	}

	/**
	 * 
	 * @return returns InputStream from rss feed
	 * @throws Exception
	 *             Exception to be thrown
	 */
	protected InputStream getInputStreamFromHTTPS() throws Exception {
		try {
			HttpGet httpGet = new HttpGet(this.rssFeedURL.toExternalForm());
			HttpResponse httpResponse = httpClient.execute(httpGet);
			HttpEntity entity = httpResponse.getEntity();
			int lastResponseCode = httpResponse.getStatusLine().getStatusCode();
			if (lastResponseCode == 400) {
				throw new Exception("NOT FOUND: " + httpResponse.getStatusLine());
			} else if (lastResponseCode == 500) {
				throw new Exception("Internal server error:  "
						+ httpResponse.getStatusLine());
			}
			InputStream input = entity.getContent();
			return input;

		} catch (IOException ex) {
			ex.printStackTrace();
			throw new Exception("Connection Error, Please Try Again Later");
		}
	}

	/**
	 * This method is used to convert the special quote characters as well as
	 * any other unordinary characters into characters that can be displayed to
	 * the end user. This also prevents the possibility of any parsing errors
	 * that can occur.
	 * 
	 * @param is
	 *            input stream to rss feed
	 * @return String a string representation of the rss feed with the converted
	 *         special characters
	 */
	public String convertSpecialChars(InputStream is) throws Exception {

		String input = "";

		BufferedReader reader = new BufferedReader(new InputStreamReader(is), 8);
		StringBuilder myStringBuild = new StringBuilder();
		char ch;
		int code;

		while ((input = reader.readLine()) != null) {
			for (int i = 0; i < input.length(); i++) {

				ch = input.charAt(i);
				code = (int) ch;

				// if newline character
				if (code == 30) {
					ch = (char) 13;
				}
				if (code == 210 || code == 211 || code == 147 || code == 148
						|| code == 8220 || code == 8221) {
					ch = (char) 34; // double quote
				} else if (code == 212 || code == 213 || code == 145
						|| code == 146 || code == 8217 || code == 65533
						|| code == 222) {
					ch = (char) 39; // single quote
				} else if (code == 226) {
					i++;

					if (i < input.length()) {
						// get next char and convert to int
						ch = input.charAt(i);
						code = (int) ch;

						// found the second character in the escape
						if (code == 128) {
							// increment for next character
							i++;

							// check to make sure we did not pass the end of the
							// input
							if (i < input.length()) {
								// get next char and convert to int
								ch = input.charAt(i);
								code = (int) ch;

								// if it is a closing single quote, replace with
								// single quote
								if (code == 153) {
									ch = (char) 39;
								}

								// if it is an opening single quote, replace
								// with
								// single quote
								if (code == 152) {
									ch = (char) 39;
								}

								else if (code == 156 || code == 157) {
									ch = (char) 34;
								}

								else {
									ch = (char) 32;
								}
							}
						}

						else {
							ch = (char) 32;
						}
					}
				} else if (code > 128) {
					ch = (char) 32;
				}

				// some of the feeds from communications are not encoded -
				// encode xml special chars
				// if (ch == '&') {
				// Log.i("!!!!!FOUND SPECIAL CHAR!!!", "found = " + ch);
				// myStringBuild.append("&amp;");
				// }
				// else if(ch == '"'){
				// Log.i("!!!!!FOUND SPECIAL CHAR!!!", "found = " + ch);
				// myStringBuild.append("&quot;");
				// }
				// else if(ch == '\''){
				// Log.i("!!!!!FOUND SPECIAL CHAR!!!", "found = " + ch);
				// myStringBuild.append("&apos;");
				// }
				// else if(ch == '<'){
				// Log.i("!!!!!FOUND SPECIAL CHAR!!!", "found = " + ch);
				// myStringBuild.append("&lt;");
				// }
				// else if(ch == '>'){
				// Log.i("!!!!!FOUND SPECIAL CHAR!!!", "found = " + ch);
				// myStringBuild.append("&gt;");
				// }
				// else {

				myStringBuild.append(ch);
				// }
			} // END FOR *****************************************
				// TEST*************************************************
			/*
			 * if(input.startsWith("<description>")){ inDescription = true; }
			 * else if(input.endsWith("</description>")){ inDescription = false;
			 * } if(inDescription && input.equals("")){
			 * myStringBuild.append("\n\n"); }
			 */
			// TEST*************************************************

		}

		// Log.i("RssFeedParser", myStringBuild.toString());

		return myStringBuild.toString();
	}

}
